﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using pl.OC.DB;
using pl.OC.Entity;
using ZedGraph;
using System.Globalization;

namespace WorkTimeApplication
{
    public partial class WorkTimePanel : UserControl
    {
        #region Class members

        private EWorkPanelType                      m_panelType;
        private DBHandler                           m_db;

        private List<WorkEntryComplexType>          m_list;
        private GraphPointsList                     m_graphPoints;

        private TimeSpan                            m_totalWorkedTime;
        private TimeSpan                            m_normalWorkedTime;
        private TimeSpan                            m_totalOvertime;
        private TimeSpan                            m_overtimeThreshold;

        private DateTime                            m_lastDBUpdate;

        private bool                                m_needToRefresh;
        private delegate void RefreshDelegate();

        private DateTime                            m_startDate;

        private WorkSheet                           m_worksheet;

        #endregion

        public WorkTimePanel()
        {
            InitializeComponent();

            m_startDate     = DateTime.Now;

            m_panelType     = EWorkPanelType.NOT_SET;
            m_list          = new List<WorkEntryComplexType>();
            m_graphPoints   = null;
        }

        private void WorkTimePanel_Load(object sender, EventArgs e)
        {
            CreateChart(zedGraphControl1);
        }

        public DBHandler DB
        {
            set 
            {
                m_db = value;

                m_db.OnDBCreated            += new DBHandler.DBCreatedDelegate(m_db_OnDBCreated);
                m_db.OnDBOpened             += new DBHandler.DBOpenedDelegate(m_db_OnDBOpened);

                m_db.OnEntryAdded           += new DBHandler.DBEntryAddedDelegate(m_db_OnEntryAdded);
                m_db.OnEntryUpdated         += new DBHandler.DBEntryUpdated(m_db_OnEntryUpdated);

                m_db.OnThresholdChange      += new DBHandler.SetThresholdDelegate(m_db_OnThresholdChange);
                
            }
        }

        void m_db_OnThresholdChange(int OldValue, int NewValue, WorkSheet ws)
        {
            m_lastDBUpdate = DateTime.Now;
            PerformFullRefresh(ws);
        }

        void m_db_OnEntryUpdated(System.IO.FileInfo dbFile, WorkSheet worksheet, WorkEntryComplexType entry, TimeSpan dt)
        {
            //don't worry about entry on the list, it is 
            //updated automaticly, remember that list stored only pointer, not a copy of object!!!!
            m_worksheet         = worksheet;
            if (m_list.Contains(entry))
            {
                //we are viewing history, so we don't need update.
                //If user switches pages, full update will be invoked.
                m_totalWorkedTime   += dt;
                m_totalOvertime     = m_totalWorkedTime - m_normalWorkedTime;
            }
            
            m_lastDBUpdate      = DateTime.Now;
            
            if (this.Visible)
            {
                ForceRefresh();
            }
        }

        void m_db_OnEntryAdded(System.IO.FileInfo dbFile, WorkSheet worksheet, WorkEntryComplexType entry)
        {
            m_list.Add(entry);
            m_worksheet         = worksheet;
            m_totalWorkedTime   += (entry.endTime - entry.startTime);
            m_totalOvertime     = m_totalWorkedTime - m_normalWorkedTime;
            m_lastDBUpdate      = DateTime.Now;
            PerformGraphFullUpdate(worksheet);

            if (this.Visible)
            {
                ForceRefresh();
            }
        }

        void m_db_OnDBOpened(System.IO.FileInfo dbFile, WorkSheet worksheet)
        {
            m_worksheet     = worksheet;
            m_lastDBUpdate  = DateTime.Now;
            PerformFullRefresh(worksheet);
        }

        void m_db_OnDBCreated(System.IO.FileInfo dbFile, WorkSheet worksheet)
        {
            m_worksheet     = worksheet;
            m_lastDBUpdate  = DateTime.Now;
            PerformFullRefresh(worksheet);
        }

        public static WorkEntryComplexType[] getWorkEntriesWithCondition(EWorkPanelType type, WorkEntryComplexType[] entries, DateTime startDate)
        {
            //http://www.geekpedia.com/code72_Get-the-week-number-of-a-given-date.html
            Predicate<WorkEntryComplexType> pred = null;

            switch (type)
            {
                case EWorkPanelType.DAILY:
                {
                    pred = delegate(WorkEntryComplexType wc)
                    {
                        DateTime dt     = wc.startTime.Date;
                        DateTime now    = startDate.Date;
                        if (dt == now)
                        {
                            return true;
                        }
                        return false;
                    };
                    break;
                }

                case EWorkPanelType.WEEKLY:
                {
                    pred = delegate(WorkEntryComplexType wc)
                    {  
                        DateTime dt         = wc.startTime.Date;
                        int dtWeekOfYear    = GetWeekNumber(dt);

                        DateTime now        = startDate.Date;
                        int nowWeekOfYear   = GetWeekNumber(now);

                        if (dtWeekOfYear == nowWeekOfYear)
                        {
                            return true;
                        }
                        return false;
                    };
                    break;
                }

                case EWorkPanelType.MONTHLY:
                {
                    pred = delegate(WorkEntryComplexType wc)
                    {
                        int MonthDt     = wc.startTime.Month;
                        int MonthNow    = startDate.Month;

                        if (MonthDt == MonthNow)
                        {
                            return true;
                        }
                        return false;
                    };
                    break;
                }

                case EWorkPanelType.OVERALL:
                case EWorkPanelType.NOT_SET:
                default:
                {
                    pred = delegate(WorkEntryComplexType wc)
                    {
                        return true;
                    };
                    break;
                }
            }

            return Array.FindAll(entries, pred);
        }
        
        protected void PerformFullRefresh(WorkSheet worksheet)
        {
            m_totalOvertime         = new TimeSpan();
            m_totalWorkedTime       = new TimeSpan();
            m_overtimeThreshold     = new TimeSpan(worksheet.OvertimeThreshold, 0, 0);
            m_normalWorkedTime      = new TimeSpan();

            m_list.Clear();

            foreach(WorkEntryComplexType WECT in getWorkEntriesWithCondition(m_panelType, worksheet.WorkEntry, m_startDate))
            {
                DateTime StartTime  = WECT.startTime;
                DateTime EndTime    = WECT.endTime;
                TimeSpan TS         = (EndTime - StartTime).Duration();

                m_totalWorkedTime += TS;

                if (m_list.Count == 0 || StartTime.DayOfYear != m_list.Last().startTime.DayOfYear)
                {
                    m_normalWorkedTime += m_overtimeThreshold;
                }

                m_list.Add(WECT);
            }

            m_totalOvertime = m_totalWorkedTime - m_normalWorkedTime;

            PerformGraphFullUpdate(worksheet);
            ForceRefresh();
        }

        protected void PerformGraphFullUpdate(WorkSheet ws)
        {
            m_graphPoints = new GraphPointsList(ws, m_panelType, m_startDate);
            UpdateChart(zedGraphControl1, m_graphPoints);
        }

        [Browsable(true)]
        public EWorkPanelType PanelType
        {
            get { return m_panelType; }
            set 
            { 
                m_panelType = value;
                if (m_panelType == EWorkPanelType.OVERALL || m_panelType == EWorkPanelType.NOT_SET)
                {
                    dateTimePickerStartDate.Enabled     = false;
                    m_startDate                         = DateTime.Now;
                }
            }
        }

        private void RefreshGUI()
        {
            labelTimePanelType.Text         = m_panelType.ToString();

            TextBoxNormalWorkedTime.Text    = FormatTimeSpan(m_normalWorkedTime);
            TextBoxTotalWorkedTime.Text     = FormatTimeSpan(m_totalWorkedTime);
            TextBoxTotalOvertime.Text       = FormatTimeSpan(m_totalOvertime);
            TextBoxLastUpdatedEntry.Text    = (m_list.Count > 0)?(m_list.Last().endTime.ToString()):("");
            TextBoxTotalEntries.Text        = m_list.Count.ToString();
            TextBoxOvertimeThreshold.Text   = m_overtimeThreshold.ToString();
            TextBoxLastUpdatedDB.Text       = m_lastDBUpdate.ToString();
        }

        private String FormatTimeSpan(TimeSpan ts)
        {
            return string.Format("{0}{1:00}:{2:00}:{3:00}", ((int)ts.TotalHours < 0) ? ("-") : (""), (int)ts.Duration().TotalHours, ts.Duration().Minutes, ts.Duration().Seconds);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            if (m_needToRefresh)
            {
                RefreshGUI();
                m_needToRefresh = false;
            }
            base.OnPaint(e);
        }

        private void ForceRefresh()
        {
            m_needToRefresh = true;
            try
            {
                //another way to call invalidate, this is faster
                this.BeginInvoke(new RefreshDelegate(this.Refresh));
            }
            catch 
            { 
            }
        }

        public void UpdateChart(ZedGraphControl zgc, IPointList PointsList)
        {
            GraphPane myPane = zgc.GraphPane;

            myPane.CurveList.Clear();
            myPane.GraphObjList.Clear();

            BarItem bar = myPane.AddBar("Wheezy", PointsList, Color.SteelBlue);
            // Fill the bar with a color gradient
            bar.Bar.Fill = new Fill(Color.RoyalBlue, Color.White, Color.RoyalBlue);

            // Add a BoxObj to show a colored band behind the graph data
            BoxObj box = new BoxObj(PointsList[0].X, Properties.Settings.Default.OvertimeThreshold, PointsList[PointsList.Count - 1].X - PointsList[0].X, Properties.Settings.Default.OvertimeThreshold, Color.Empty, Color.PowderBlue);
            box.Location.CoordinateFrame = CoordType.AxisXYScale;
            // Align the left-top of the box to (0, 110)
            box.Location.AlignH = AlignH.Left;
            box.Location.AlignV = AlignV.Top;
            // place the box behind the axis items, so the grid is drawn on top of it
            box.ZOrder = ZOrder.G_BehindChartFill;
            box.IsClippedToChartRect = true;
            myPane.GraphObjList.Add(box);

            BarItem.CreateBarLabels(myPane, true, "f2");
            

            zedGraphControl1.AxisChange();
            zedGraphControl1.Invalidate();
        }

        public void CreateChart(ZedGraphControl zgc)
        {
            GraphPane myPane = zgc.GraphPane;

            // Set the title and axis labels
            myPane.XAxis.Title.Text = "Date";
            myPane.YAxis.Title.Text = "WorkTime";


            myPane.XAxis.Type = AxisType.Date;

            // Turn off the axis background fill
            myPane.Chart.Fill.IsVisible = false;
            // Hide the legend
            myPane.Legend.IsVisible = false;

            // Hide the title
            myPane.Title.IsVisible = false;

            // Show the grid lines
            myPane.XAxis.MajorGrid.IsVisible = true;
            myPane.YAxis.MajorGrid.IsVisible = true;

            // Make it a stacked bar
            myPane.BarSettings.Type = BarType.Overlay;

            // Calculate the Axis Scale Ranges
            zgc.AxisChange();
        }

        public static int GetWeekNumber(DateTime dtPassed)
        {
            CultureInfo ciCurr = CultureInfo.CurrentCulture;
            int weekNum = ciCurr.Calendar.GetWeekOfYear(dtPassed, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
            return weekNum;
        }

        private void dateTimePickerStartDate_ValueChanged(object sender, EventArgs e)
        {
            m_startDate = this.dateTimePickerStartDate.Value;
            PerformFullRefresh(m_worksheet);
        }

        public ZedGraphControl ZedGraph
        {
            get { return this.zedGraphControl1; }
        }

        /// <summary>
        /// Fired when maximizing
        /// </summary>
        /// <param name="e"></param>
        protected override void OnVisibleChanged(EventArgs e)
        {
            ForceRefresh();
            base.OnVisibleChanged(e);
        }

    }

    #region EWorkPanelType - helper enum

    public enum EWorkPanelType
    {
        OVERALL, DAILY, WEEKLY, MONTHLY, NOT_SET
    }

    #endregion

    #region GraphPointsList - list of points for ZedGraph

    public class GraphPointsList : IPointList
    {
        private List<GraphHelperPoint> m_list;

        public GraphPointsList(WorkSheet ws, EWorkPanelType panelType, DateTime startDate)
        {
            m_list = new List<GraphHelperPoint>();

            GraphHelperPoint gp = null;
            int index = -1;

            foreach (WorkEntryComplexType wect in WorkTimePanel.getWorkEntriesWithCondition(panelType, ws.WorkEntry, startDate))
            {
                index = getIndex(wect);
                if (index == -1)
                {
                    gp = new GraphHelperPoint();
                    m_list.Add(gp);
                }
                else
                {
                    gp = m_list.ElementAt(index);
                }

                gp.AddWorkItem(wect);
            }
        }

        public int Count
        {
            get { return m_list.Count; }
        }

        public PointPair this[int index]
        {
            get
            {
                if (index >= Count || index < 0)
                {
                    return new PointPair();
                }

                double x = m_list.ElementAt(index).X;
                double y = m_list.ElementAt(index).Y;
                return new PointPair(x, y);
            }
        }

        public object Clone()
        {
            throw new NotImplementedException();
        }

        public int getIndex(WorkEntryComplexType wect)
        {
            int indexer = 0;
            foreach (GraphHelperPoint gp in m_list)
            {
                if (wect.startTime.Date == gp.getStartTime().Date)
                {
                    return indexer;
                }
                ++indexer;
            }
            return -1;
        }
    }

    #endregion

    #region GraphHelperPoint - helper class for point representation for ZedGraph

    public class GraphHelperPoint
    {
        private LinkedList<WorkEntryComplexType> m_items;

        public GraphHelperPoint()
        {
            m_items = new LinkedList<WorkEntryComplexType>();
        }

        public DateTime getStartTime()
        {
            return m_items.ElementAt(0).startTime;
        }

        public void AddWorkItem(WorkEntryComplexType wt)
        {
            if (!m_items.Contains(wt))
            {
                foreach (WorkEntryComplexType wect in m_items)
                {
                    if (wt.startTime.Date != wect.startTime.Date)
                    {
                        throw new ApplicationException("Dates must be the same, check data: " + wt.startTime.Date + " vs " + wect.startTime.Date);
                    }
                }

                m_items.AddLast(wt);
            }
        }

        public XDate X
        {
            get
            {
                DateTime dt     = m_items.ElementAt(0).startTime;
                XDate ret       = XDate.DateTimeToXLDate(dt.Date);
                return ret;
            }
        }

        public double Y
        {
            get
            {
                TimeSpan mainTS = new TimeSpan();

                foreach (WorkEntryComplexType wect in m_items)
                {
                    mainTS += (wect.endTime - wect.startTime);
                }

                return mainTS.TotalHours;
            }
        }
    }

    #endregion
}
